package com.example.common.support;

import com.example.common.JwtProperties;
import com.example.common.JwtTokenUtil;
import com.example.common.util.CP_LoggerUtils;
import com.example.function.sys.model.one.SysLogger;
import com.example.function.sys.service.ISysLoggerService;
import com.example.support.CP_AuthenticationUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.stream.Collectors;

/**
 * <p>操作日志拦截器</p>
 * Created by hanqf on 2020/6/22 16:58.
 */
@Slf4j
public class CP_OperateLogInterceptor implements HandlerInterceptor {

    //请求开始时间标识
    public static final String LOGGER_SEND_TIME = "_send_time";
    //请求日志实体标识
    public static final String LOGGER_ENTITY = "_logger_entity";

    /**
     * Log日志参数标记-本地化信息
     */
    private String mdcUserLocale = "userName";

    /**
     * Log日志参数标记-用户名称
     */
    private String mdcUserName = "userLocale";

    @Autowired
    MessageSource messageSource;
    @Autowired
    ObjectMapper objectMapper;

    @Autowired
    JwtTokenUtil jwtTokenUtil;
    @Autowired
    private JwtProperties jwtProperties;

    @Value("${operate.log.open}")
    private boolean operate_log_open;

    /**
     * 根据传入的类型获取spring管理的对应dao
     *
     * @param clazz   类型
     * @param request 请求对象
     * @param <T>
     * @return
     */
    private <T> T getBean(Class<T> clazz, HttpServletRequest request) {
        BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());
        return factory.getBean(clazz);
    }

    private String getUserName(HttpServletRequest request) {
        String username = CP_AuthenticationUtils.getUserLoginName();
        if (username == null) {
            String jwtToken = request.getHeader(jwtProperties.getHeader());
            if (!StringUtils.hasText(jwtToken)) {
                jwtToken = request.getParameter("token");
            }

            if (StringUtils.hasText(jwtToken)) {
                //去掉前缀
                if (jwtToken.startsWith("Bearer ")) {
                    jwtToken = jwtToken.substring(7);
                }

                username = jwtTokenUtil.getUsernameFromToken(jwtToken);
            }
        }

        if (username == null && request.getAttribute("username") != null) {
            username = (String) request.getAttribute("username");
        }
        return username;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        MDC.put(mdcUserName, org.apache.commons.lang3.StringUtils.defaultString(getUserName(request),
                "anonymous"));
        MDC.put(mdcUserLocale, org.apache.commons.lang3.StringUtils.defaultString(LocaleContextHolder
                .getLocale().getLanguage(), "default"));

        if (operate_log_open) { //是否需要记录审计日志
            //创建日志实体
            SysLogger sysLogger = new SysLogger();
            //获取请求sessionId
            String sessionId = request.getSession().getId();
            //请求路径
            String url = request.getServletPath();
            //获取请求参数信息
            String paramData = objectMapper.writeValueAsString(request.getParameterMap());

            final String contentType = request.getContentType();

            if ("application/json".equals(contentType)) {
                //正常情况下，请求流被读过一次就不能再读了,这里可以是因为加了CP_BodyReaderFilter
                String request_json = request.getReader()
                        .lines().parallel().collect(Collectors.joining(System.lineSeparator()));
                sysLogger.setLogRequestJson(request_json);
            }

            //设置客户端ip
            sysLogger.setLogClientIp(CP_LoggerUtils.getCliectIp(request));
            //设置请求方法
            sysLogger.setLogMethod(request.getMethod());
            //设置请求类型（json|普通请求）
            sysLogger.setLogType(contentType);
            //设置请求参数内容json字符串
            sysLogger.setLogParamData(paramData);
            //设置请求地址
            sysLogger.setLogUri(url);
            //设置sessionId
            sysLogger.setLogSessionId(sessionId);

            //设置请求开始时间
            Date date = new Date();
            sysLogger.setLogTime(date);

            request.setAttribute(LOGGER_SEND_TIME, date.getTime());
            //设置请求实体到request内，方面afterCompletion方法调用
            request.setAttribute(LOGGER_ENTITY, sysLogger);
        }

        //是否需要记录基于注解的操作日志
        if (getCP_OperateLogAnnotation(handler) != null) {
            log.info(getLog(request, handler) + "  start");
        }


        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        String username = getUserName(request);

        String defaultMessage = "";
        String type = "";
        CP_OperateLog operateLog = getCP_OperateLogAnnotation(handler);

        if (operateLog != null) {
            log.info(getLog(request, handler) + "  end");
            defaultMessage = operateLog.value();
            type = operateLog.type().getDesc();
        }


        if (username != null && !"anonymousUser".equals(username) && operate_log_open) {
            //获取请求错误码
            int status = response.getStatus();
            //当前时间
            long currentTime = System.currentTimeMillis();
            //请求开始时间
            long time = Long.valueOf(request.getAttribute(LOGGER_SEND_TIME).toString());
            //获取本次请求日志实体
            SysLogger sysLogger = (SysLogger) request.getAttribute(LOGGER_ENTITY);
            //设置请求时间差
            sysLogger.setLogTimeConsuming(Integer.valueOf((currentTime - time) + ""));
            //设置返回时间
            sysLogger.setLogReturmTime(currentTime + "");
            //设置返回错误码
            sysLogger.setLogHttpStatusCode(status + "");
            //设置返回值
            //sysLogger.setLogReturnData(objectMapper.writeValueAsString(request.getAttribute(CP_LoggerUtils.LOGGER_RETURN)));
            sysLogger.setLogDesc(defaultMessage + "--" + type);
            sysLogger.setLogUser(username);

            //执行将日志写入数据库
            ISysLoggerService sysLoggerService = getBean(ISysLoggerService.class, request);

            //记录审计日志
            sysLoggerService.add(sysLogger);
            //log.info(objectMapper.writeValueAsString(sysLogger));

        }
    }

    /**
     * 验证是否存在@CP_OperateLog注解
     *
     * @param handler
     * @return
     */
    private CP_OperateLog getCP_OperateLogAnnotation(Object handler) {
        // 将handler强转为HandlerMethod, 前面已经证实这个handler就是HandlerMethod
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 从方法处理器中获取出要调用的方法
            Method method = handlerMethod.getMethod();
            // 获取出方法上的CP_OperateLog注解
            CP_OperateLog operateLog = method.getAnnotation(CP_OperateLog.class);
            if (operateLog != null) {
                return operateLog;
            }
        }
        return null;
    }

    /**
     * 构建日志内容
     *
     * @param request
     * @param handler
     * @return
     */
    private String getLog(HttpServletRequest request, Object handler) {
        String username = getUserName(request);
        // 将handler强转为HandlerMethod, 前面已经证实这个handler就是HandlerMethod
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        // 从方法处理器中获取出要调用的方法
        Method method = handlerMethod.getMethod();
        // 获取出方法上的CP_OperateLog注解
        CP_OperateLog operateLog = method.getAnnotation(CP_OperateLog.class);
        String defaultMessage = operateLog.value();
        String methodName = handlerMethod.getBean().getClass().getName() + "."
                + method.getName();


        String desc = this.handleDescription(operateLog.key(), StringUtils
                .hasText(defaultMessage) ? defaultMessage : methodName);
        // 装配日志信息
        String logline = this.buildLogLine(username, operateLog.type().getDesc(), desc);

        return logline;
    }

    /**
     * 描述 : <构建日志行>. <br>
     * <p>
     * <使用方法说明>
     * </p>
     *
     * @param username    用户名称
     * @param operateType 操作类型
     * @param description 操作描述
     * @return 日志行： username - operateType - description
     */
    protected String buildLogLine(String username, String operateType,
                                  String description) {
        StringBuilder sb = new StringBuilder();
        sb.append(username).append(" - ").append(operateType).append(" - ")
                .append(description);
        return sb.toString();
    }

    /**
     * 描述 : <获取日志内容描述，可以从消息配置文件中找到对应的信息 >. <br>
     * <p>
     * <使用方法说明>
     * </p>
     *
     * @param key            日志内容key
     * @param defaultMessage 默认的描述信息
     * @return 描述信息
     */
    protected String handleDescription(String key, String defaultMessage) {
        if (messageSource == null) {
            return defaultMessage;
        }
        if (!StringUtils.hasText(key)) {
            return defaultMessage;
        }
        //从消息配置文件中获取内容
        String message = messageSource.getMessage(key, new Object[]{}, defaultMessage, LocaleContextHolder.getLocale());
        if (!StringUtils.hasText(message)) {
            return defaultMessage;
        } else {
            return message;
        }
    }

}
